package com.k8.registry.listener;

import com.alibaba.fastjson2.JSON;
import com.alibaba.fastjson2.JSONObject;
import com.alibaba.fastjson2.TypeReference;
import com.k8.common.event.Event;
import com.k8.common.event.SynListener;
import com.k8.common.util.StrUtil;
import com.k8.registry.AbstractRegistry;
import com.k8.rpc.metadata.AppMetaData;
import lombok.extern.slf4j.Slf4j;

import java.io.*;
import java.util.List;
import java.util.Properties;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

@Slf4j
public class NotifyListener implements SynListener {
    private final Lock lock = new ReentrantLock();

    @Override
    public boolean canDispose(Event event) {
        if (event instanceof NotifyEvent) return true;
        return false;
    }

    @Override
    public boolean judgeEventType(Event event) {
        return true;
    }

    /**
     * 不同于dubbo，通过文件锁的形式保证文件更改是串行的
     * 此外通过版本version以一种类似乐观锁的形式来控制同一时刻只能有一个线程能更改文件，但是为了确保正在更改时有更新的版本更新，所有使用
     * 上诉排他文件锁来实现串行
     * k8是基于事件处理的同步机制来保证同一时刻只有一个线程能处理文件，不需要版本控制
     */
    @Override
    public void disposal(Event event) {
        NotifyEvent notifyEvent = (NotifyEvent) event;
        List<AppMetaData> appMetaDataList = notifyEvent.getValue();
        AbstractRegistry.SaveToLocalContext context = notifyEvent.getContext();
        String apiSignature = notifyEvent.getApiSignature();
        if (!notifyEvent.isRead()) {
            int maxRetryTimesSaveProperties = AbstractRegistry.SaveToLocalContext.MAX_RETRY_TIMES_SAVE_PROPERTIES;
            long scheduleTime = context.getScheduleTime();
            doSaveDisposal(0, maxRetryTimesSaveProperties, scheduleTime, appMetaDataList, context, apiSignature);
        } else {
            Properties properties = new Properties();
            File cacheFile = context.getFile();
            try {
                FileInputStream fileInputStream = new FileInputStream(cacheFile);
                properties.load(fileInputStream);
                String metadatas = properties.getProperty(apiSignature);
                if (StrUtil.isNotEmpty(metadatas)) {
                    List<AppMetaData> appMetaData = JSON.parseObject(metadatas, new TypeReference<>() {
                    });
                    appMetaDataList.addAll(appMetaData);
                }
            } catch (FileNotFoundException e) {
                throw new RuntimeException(e);
            } catch (IOException e) {
                throw new RuntimeException(e);
            }
        }
    }

    private void doSaveDisposal(int retry, int max, long scheduleTime, List<AppMetaData> appMetaDataList, AbstractRegistry.SaveToLocalContext context, String apiSignature) {
        try {
            doSave(context, appMetaDataList, apiSignature);
        } catch (Throwable throwable) {
            if (retry < max) {
                try {
                    Thread.sleep(scheduleTime);
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
                doSaveDisposal(retry + 1, max, scheduleTime, appMetaDataList, context, apiSignature);
            } else {
                //todo
                log.warn("Registry instances to local error.");
            }
        }
    }

    private void doSave(AbstractRegistry.SaveToLocalContext context, List<AppMetaData> appMetaDataList, String apiSignature) throws IOException {
        File cacheInstancesFile = context.getFile();
        Properties metadataProperties = new Properties();
        FileInputStream fileInputStream = new FileInputStream(cacheInstancesFile);
        metadataProperties.load(fileInputStream);
        String metadatas = JSON.toJSONString(appMetaDataList);
        if (metadataProperties.contains(apiSignature)) {
            metadataProperties.replace(apiSignature, metadatas);
        } else {
            metadataProperties.put(apiSignature, metadatas);
        }
        FileOutputStream fileOutputStream = new FileOutputStream(cacheInstancesFile);
        metadataProperties.store(fileOutputStream, "Registry cache");
    }

    @Override
    public Lock getLock() {
        return this.lock;
    }

    public static class NotifyEvent implements Event {

        private final List<AppMetaData> remoteAppInstances;
        private final AbstractRegistry.SaveToLocalContext saveToLocalContext;
        private String apiSignature;
        private boolean read;

        @Override
        public Throwable getException() {
            return null;
        }

        public boolean isRead() {
            return read;
        }

        public NotifyEvent(List<AppMetaData> remoteAppInstances, AbstractRegistry.SaveToLocalContext saveToLocalContext, String signature, boolean read) {
            this.remoteAppInstances = remoteAppInstances;
            this.saveToLocalContext = saveToLocalContext;
            this.apiSignature = signature;
            this.read = read;
        }

        public String getApiSignature() {
            return apiSignature;
        }


        @Override
        public List<AppMetaData> getValue() {
            return this.remoteAppInstances;
        }

        public AbstractRegistry.SaveToLocalContext getContext() {
            return this.saveToLocalContext;
        }
    }
}